use std::cmp::max;
use std::fs::File;
use std::io::Write;
use std::path::Path;

use anyhow::Error;
use image::{GenericImageView, ImageBuffer};
use serde_json::{json, Value};

use crate::common::{change_extension, collect_images, Image, Rect};
use crate::error::Res;

pub fn pack_images<P: AsRef<Path>>(source_path: P, image_name_pattern: &str, output_file_name: P) -> Res<()> {
    let mut images = collect_images(source_path, image_name_pattern)?;
    if images.is_empty() {
        return Err(Error::msg("No matching images found"));
    }
    let (width, height) = arrange_images(&mut images, 2048);
    let mut merged_image = ImageBuffer::new(width, height);

    for image in &images {
        let Rect { x, y, .. } = &image.rect;
        for (xi, yi, pixel) in image.buffer.pixels() {
            merged_image.put_pixel(x + xi, y + yi, pixel);
        }
    }

    merged_image.save(&output_file_name)?;
    save_images_rect(output_file_name, &mut images)?;

    Ok(())
}

fn save_images_rect<P: AsRef<Path>>(output_file_name: P, images: &mut Vec<Image>) -> Res<()> {
    let desc_file_name = change_extension(output_file_name, "json");
    let mut file = File::create(desc_file_name)?;
    let json: Vec<Value> = images.iter().map(|i| json!({
        "name":i.name.clone(),
        "rect":[i.rect.x, i.rect.y, i.rect.w, i.rect.h],
    })).collect();
    let buffer = serde_json::to_vec(&json)?;
    file.write_all(&buffer)?;
    Ok(())
}

fn arrange_images(images: &mut Vec<Image>, max_width: u32) -> (u32, u32) {
    let mut x = 0;
    let mut y = 0;
    let mut row_height = 0;
    let mut total_width = 0;

    for image in images.iter_mut() {
        let rect = &mut image.rect;
        let w = rect.w;
        let h = rect.h;
        let x_right = x + w;
        if x_right > max_width {
            y = y + row_height;
            x = 0;
            row_height = 0;
            continue;
        }
        rect.x = x;
        rect.y = y;
        x += w;
        total_width = max(total_width, x);
        row_height = max(row_height, h);
    }
    y = y + row_height;
    (total_width, y)
}

